#include "wx/wxprec.h"
#include "wx/vscroll.h"

BEGIN_EVENT_TABLE( wxVScrolledWindow, wxPanel )
  EVT_SIZE( wxVScrolledWindow::OnSize )
  EVT_SCROLLWIN( wxVScrolledWindow::OnScroll )
#if wxUSE_MOUSEWHEEL
  EVT_MOUSEWHEEL( wxVScrolledWindow::OnMouseWheel )
#endif
END_EVENT_TABLE()

IMPLEMENT_ABSTRACT_CLASS( wxVScrolledWindow, wxPanel )

// ----------------------------------------------------------------------------
// initialization
// ----------------------------------------------------------------------------

void wxVScrolledWindow::Init() {
  // we're initially empty
  m_lineMax =
    m_lineFirst = 0;
  // this one should always be strictly positive
  m_nVisible = 1;
  m_heightTotal = 0;
  #if wxUSE_MOUSEWHEEL
  m_sumWheelRotation = 0;
  #endif
}

// ----------------------------------------------------------------------------
// various helpers
// ----------------------------------------------------------------------------

wxCoord wxVScrolledWindow::EstimateTotalHeight() const {
  // estimate the total height: it is impossible to call
  // OnGetLineHeight() for every line because there may be too many of
  // them, so we just make a guess using some lines in the beginning,
  // some in the end and some in the middle
  static const size_t NUM_LINES_TO_SAMPLE = 10;
  wxCoord heightTotal;
  if( m_lineMax < 3 * NUM_LINES_TO_SAMPLE ) {
    // in this case calculating exactly is faster and more correct than
    // guessing
    heightTotal = GetLinesHeight( 0, m_lineMax );
  } else { // too many lines to calculate exactly
    // look at some lines in the beginning/middle/end
    heightTotal =
      GetLinesHeight( 0, NUM_LINES_TO_SAMPLE ) +
      GetLinesHeight( m_lineMax - NUM_LINES_TO_SAMPLE, m_lineMax ) +
      GetLinesHeight( m_lineMax / 2 - NUM_LINES_TO_SAMPLE / 2,
                      m_lineMax / 2 + NUM_LINES_TO_SAMPLE / 2 );
    // use the height of the lines we looked as the average
    heightTotal = ( wxCoord )
                  ( ( ( float )heightTotal / ( 3 * NUM_LINES_TO_SAMPLE ) ) * m_lineMax );
  }
  return heightTotal;
}

wxCoord wxVScrolledWindow::GetLinesHeight( size_t lineMin, size_t lineMax ) const {
  if( lineMin == lineMax ) {
    return 0;
  } else if( lineMin > lineMax ) {
    return -GetLinesHeight( lineMax, lineMin );
  }
  //else: lineMin < lineMax
  // let the user code know that we're going to need all these lines
  OnGetLinesHint( lineMin, lineMax );
  // do sum up their heights
  wxCoord height = 0;
  for( size_t line = lineMin; line < lineMax; line++ ) {
    height += OnGetLineHeight( line );
  }
  return height;
}

size_t wxVScrolledWindow::FindFirstFromBottom( size_t lineLast, bool full ) {
  const wxCoord hWindow = GetClientSize().y;
  // go upwards until we arrive at a line such that lineLast is not visible
  // any more when it is shown
  size_t lineFirst = lineLast;
  wxCoord h = 0;
  for( ;; ) {
    h += OnGetLineHeight( lineFirst );
    if( h > hWindow ) {
      // for this line to be fully visible we need to go one line
      // down, but if it is enough for it to be only partly visible then
      // this line will do as well
      if( full ) {
        lineFirst++;
      }
      break;
    }
    if( !lineFirst ) {
      break;
    }
    lineFirst--;
  }
  return lineFirst;
}

void wxVScrolledWindow::RemoveScrollbar() {
  m_lineFirst = 0;
  m_nVisible = m_lineMax;
  SetScrollbar( wxVERTICAL, 0, 0, 0 );
}

void wxVScrolledWindow::UpdateScrollbar() {
  // see how many lines can we fit on screen
  const wxCoord hWindow = GetClientSize().y;
  wxCoord h = 0;
  size_t line;
  for( line = m_lineFirst; line < m_lineMax; line++ ) {
    if( h > hWindow ) {
      break;
    }
    h += OnGetLineHeight( line );
  }
  // if we still have remaining space below, maybe we can fit everything?
  if( h < hWindow ) {
    wxCoord hAll = h;
    for( size_t lineFirst = m_lineFirst; lineFirst > 0; lineFirst-- ) {
      hAll += OnGetLineHeight( m_lineFirst - 1 );
      if( hAll > hWindow ) {
        break;
      }
    }
    if( hAll < hWindow ) {
      // we don't need scrollbar at all
      RemoveScrollbar();
      return;
    }
  }
  m_nVisible = line - m_lineFirst;
  int pageSize = m_nVisible;
  if( h > hWindow ) {
    // last line is only partially visible, we still need the scrollbar and
    // so we have to "fix" pageSize because if it is equal to m_lineMax the
    // scrollbar is not shown at all under MSW
    pageSize--;
  }
  // set the scrollbar parameters to reflect this
  SetScrollbar( wxVERTICAL, m_lineFirst, pageSize, m_lineMax );
}

// ----------------------------------------------------------------------------
// operations
// ----------------------------------------------------------------------------

void wxVScrolledWindow::SetLineCount( size_t count ) {
  // save the number of lines
  m_lineMax = count;
  // and our estimate for their total height
  m_heightTotal = EstimateTotalHeight();
  // recalculate the scrollbars parameters
  if( count ) {
    m_lineFirst = 1;    // make sure it is != 0
    ScrollToLine( 0 );
  } else { // no items
    RemoveScrollbar();
  }
}

void wxVScrolledWindow::RefreshLine( size_t line ) {
  // is this line visible?
  if( !IsVisible( line ) ) {
    // no, it is useless to do anything
    return;
  }
  // calculate the rect occupied by this line on screen
  wxRect rect;
  rect.width = GetClientSize().x;
  rect.height = OnGetLineHeight( line );
  for( size_t n = GetVisibleBegin(); n < line; n++ ) {
    rect.y += OnGetLineHeight( n );
  }
  // do refresh it
  RefreshRect( rect );
}

void wxVScrolledWindow::RefreshLines( size_t from, size_t to ) {
  wxASSERT_MSG( from <= to, _T( "RefreshLines(): empty range" ) );
  // clump the range to just the visible lines -- it is useless to refresh
  // the other ones
  if( from < GetVisibleBegin() ) {
    from = GetVisibleBegin();
  }
  if( to >= GetVisibleEnd() ) {
    to = GetVisibleEnd();
  } else
  { to++; }
  // calculate the rect occupied by these lines on screen
  wxRect rect;
  rect.width = GetClientSize().x;
  for( size_t nBefore = GetVisibleBegin(); nBefore < from; nBefore++ ) {
    rect.y += OnGetLineHeight( nBefore );
  }
  for( size_t nBetween = from; nBetween < to; nBetween++ ) {
    rect.height += OnGetLineHeight( nBetween );
  }
  // do refresh it
  RefreshRect( rect );
}

void wxVScrolledWindow::RefreshAll() {
  UpdateScrollbar();
  Refresh();
}

bool wxVScrolledWindow::Layout() {
  if( GetSizer() ) {
    // adjust the sizer dimensions/position taking into account the
    // virtual size and scrolled position of the window.
    int w = 0, h = 0;
    GetVirtualSize( &w, &h );
    // x is always 0 so no variable needed
    int y = -GetLinesHeight( 0, GetFirstVisibleLine() );
    GetSizer()->SetDimension( 0, y, w, h );
    return true;
  }
  // fall back to default for LayoutConstraints
  return wxPanel::Layout();
}

int wxVScrolledWindow::HitTest( wxCoord ( x ), wxCoord y ) const {
  const size_t lineMax = GetVisibleEnd();
  for( size_t line = GetVisibleBegin(); line < lineMax; line++ ) {
    y -= OnGetLineHeight( line );
    if( y < 0 ) {
      return line;
    }
  }
  return wxNOT_FOUND;
}

// ----------------------------------------------------------------------------
// scrolling
// ----------------------------------------------------------------------------

bool wxVScrolledWindow::ScrollToLine( size_t line ) {
  if( !m_lineMax ) {
    // we're empty, code below doesn't make sense in this case
    return false;
  }
  // determine the real first line to scroll to: we shouldn't scroll beyond
  // the end
  size_t lineFirstLast = FindFirstFromBottom( m_lineMax - 1, true );
  if( line > lineFirstLast ) {
    line = lineFirstLast;
  }
  // anything to do?
  if( line == m_lineFirst ) {
    // no
    return false;
  }
  // remember the currently shown lines for the refresh code below
  size_t lineFirstOld = GetVisibleBegin(),
         lineLastOld = GetVisibleEnd();
  m_lineFirst = line;
  // the size of scrollbar thumb could have changed
  UpdateScrollbar();
  // finally refresh the display -- but only redraw as few lines as possible
  // to avoid flicker
  if( GetChildren().empty() &&
      ( GetVisibleBegin() >= lineLastOld || GetVisibleEnd() <= lineFirstOld ) ) {
    Refresh();
  } else { // overlap between the lines we showed before and should show now
    ScrollWindow( 0, GetLinesHeight( GetVisibleBegin(), lineFirstOld ) );
  }
  return true;
}

bool wxVScrolledWindow::ScrollLines( int lines ) {
  lines += m_lineFirst;
  if( lines < 0 ) {
    lines = 0;
  }
  return ScrollToLine( lines );
}

bool wxVScrolledWindow::ScrollPages( int pages ) {
  bool didSomething = false;
  while( pages ) {
    int line;
    if( pages > 0 ) {
      line = GetVisibleEnd();
      if( line ) {
        line--;
      }
      pages--;
    } else { // pages < 0
      line = FindFirstFromBottom( GetVisibleBegin() );
      pages++;
    }
    didSomething = ScrollToLine( line );
  }
  return didSomething;
}

// ----------------------------------------------------------------------------
// event handling
// ----------------------------------------------------------------------------

void wxVScrolledWindow::OnSize( wxSizeEvent& event ) {
  UpdateScrollbar();
  event.Skip();
}

void wxVScrolledWindow::OnScroll( wxScrollWinEvent& event ) {
  size_t lineFirstNew;
  const wxEventType evtType = event.GetEventType();
  if( evtType == wxEVT_SCROLLWIN_TOP ) {
    lineFirstNew = 0;
  } else if( evtType == wxEVT_SCROLLWIN_BOTTOM ) {
    lineFirstNew = m_lineMax;
  } else if( evtType == wxEVT_SCROLLWIN_LINEUP ) {
    lineFirstNew = m_lineFirst ? m_lineFirst - 1 : 0;
  } else if( evtType == wxEVT_SCROLLWIN_LINEDOWN ) {
    lineFirstNew = m_lineFirst + 1;
  } else if( evtType == wxEVT_SCROLLWIN_PAGEUP ) {
    lineFirstNew = FindFirstFromBottom( m_lineFirst );
  } else if( evtType == wxEVT_SCROLLWIN_PAGEDOWN ) {
    lineFirstNew = GetVisibleEnd();
    if( lineFirstNew ) {
      lineFirstNew--;
    }
  } else if( evtType == wxEVT_SCROLLWIN_THUMBRELEASE ) {
    lineFirstNew = event.GetPosition();
  } else if( evtType == wxEVT_SCROLLWIN_THUMBTRACK ) {
    lineFirstNew = event.GetPosition();
  } else { // unknown scroll event?
    wxFAIL_MSG( _T( "unknown scroll event type?" ) );
    return;
  }
  ScrollToLine( lineFirstNew );
}

#if wxUSE_MOUSEWHEEL

void wxVScrolledWindow::OnMouseWheel( wxMouseEvent& event ) {
  m_sumWheelRotation += event.GetWheelRotation();
  int delta = event.GetWheelDelta();
  // how much to scroll this time
  int units_to_scroll = -( m_sumWheelRotation / delta );
  if( !units_to_scroll ) {
    return;
  }
  m_sumWheelRotation += units_to_scroll * delta;
  if( !event.IsPageScroll() ) {
    ScrollLines( units_to_scroll * event.GetLinesPerAction() );
  } else
    // scroll pages instead of lines
  { ScrollPages( units_to_scroll ); }
}

#endif // wxUSE_MOUSEWHEEL

